<?php
namespace hid\builders;

use hid\base\IdBuilder;
use hid\base\IdStorage;
use hid\base\LocalStorage;

/**
 * 号段id生成器
 * @method  LocalStorage getStorage()
 */
class SegmentBuilder extends IdBuilder
{
    protected $storage = 'memory';

    /**
     * 号段存储器类型
     * @var string
     */
    protected $remoteStorage = '';

    /**
     * 远程id储存器对象
     * @var IdStorage
     */
    protected $remoteStorageCache;

    /**
     * 远程id生成器对象
     * @var IdBuilder
     */
    protected $remoteIdBuilder;

    /**
     * 每次从远程获取的id数量
     * @var int
     */
    protected $count = 2000;

    /**
     * 预加载号段次数
     * @var int
     */
    protected $preload = 1;

    protected $isInitValueBak = false;

    protected $maxidKey = 'maxid';
    protected $segmentsKey = 'segments';

    /**
     * 是否已经预加载过号段
     * @var bool
     */
    protected $preloaded = false;


    public function __construct(array $config = [])
    {
        parent::__construct($config);

        $this->isInitValueBak = $this->isInitValue;

        // 主动关闭主动设置初始值功能
        $this->isInitValue = false;
    }

    public function setCount(int $count):self
    {
        $this->count = $count;

        return $this;
    }

    public function getCount():int
    {
        return $this->count;
    }

    public function getPreload():int
    {
        return $this->preload;
    }

    protected function getRemoteStorage():IdStorage
    {
        if (is_null($this->remoteStorageCache)) {
            $this->remoteStorageCache = $this->idManager->getIdStorage($this->remoteStorage);
        }

        return $this->remoteStorageCache;
    }

    protected function getRemoteIdBuilder():IdBuilder
    {
        if (is_null($this->remoteIdBuilder)) {
            $this->remoteIdBuilder = new IdBuilder([
                'idManager' => $this->idManager,
                'name' => $this->name,
                'step' => $this->step,
                'storage' => $this->remoteStorage,
                'isInitValue' => $this->isInitValueBak,
                'initValue' => $this->initValue,
            ]);
        }

        return $this->remoteIdBuilder;
    }

    protected function loadRemoteSegment():bool
    {
        $remoteStorage = $this->getRemoteStorage();
        $length = $this->step * $this->count;
        // 远程读取$length数量的id
        $maxId = $remoteStorage->incrBy($this->getRemoteIdBuilder(),$length);
        if (is_null($maxId)) {
            return false;
        }

        // 追加号段至本地存储器
        $minId = $maxId - $length;
        $localStorage = $this->getStorage();
        $segmentsKey = $this->buildKey($this->segmentsKey);
        $segments = $localStorage->getValue($segmentsKey);
        $segments = empty($segments) ? [] : explode(',',$segments);
        $segments[] = $minId . '-' . $maxId;
        $localStorage->setValue($segmentsKey,implode(',',$segments));

        return true;
    }

    /**
     * 切换下一个号段
     */
    protected function switchNextSegment():bool
    {
        $localStorage = $this->getStorage();
        $segmentsKey = $this->buildKey($this->segmentsKey);
        $segments = $localStorage->getValue($segmentsKey);
        if (empty($segments)) {
            return false;
        }
        $segments = explode(',',$segments);
        // 弹出下一个的号段
        $segment = array_shift($segments);
        $localStorage->setValue($segmentsKey,implode(',',$segments));
        list($minId,$maxId) = explode('-',$segment);
        $localStorage->setValue($this->buildMaxidKey(),(int)$maxId);
        $localStorage->setValue($this->buildCuridKey(),(int)$minId);

        return true;
    }

    /**
     * 预加载号段
     */
    protected function preloadSegment():bool
    {
        $result = false;
        for ($i = 0;$i < $this->preload;$i++) {
            if ($this->loadRemoteSegment()) {
                $result = true;
            }
        }

        return $result;
    }

    /**
     * 初始化预加载
     * @return bool
     */
    protected function initPreload():void
    {
        if ($this->preloaded === true) {
            return;
        }

        $localStorage = $this->getStorage();
        if ($localStorage->exists($this->buildKey('initialized'))) {
            $this->preloaded = true;
            return ;
        }

        if ($this->lock('initlock')) {
            try {
                $this->preloadSegment();
                $this->switchNextSegment();
                $this->lock('initialized');
            } finally {
                $this->unlock('initlock');
            }

            $this->preloaded = true;
        }
    }

    /**
     * 加载并切换号段
     * return bool 是否切换了号段
     */
    protected function loadSwitchSegment():bool
    {
        // 定义号段切换成功状态
        $switchedSegmentStatus = false;
        if ($this->lock('loadRemoteSegment')) {
            try {
                // 切换下一个号段,如果切换失败,说明没有号段,则继续等待
                $switchedSegmentStatus = $this->switchNextSegment();
                $this->loadRemoteSegment();
                if ($switchedSegmentStatus === false) {
                    $switchedSegmentStatus = $this->switchNextSegment();
                }
            } finally {
                $this->unlock('loadRemoteSegment');
            }
        }

        return $switchedSegmentStatus;
    }

    protected function buildMaxidKey():string
    {
        return $this->buildKey($this->maxidKey)  ;
    }

    protected function buildCuridKey():string
    {
        return $this->getStoreKey()  ;
    }

    protected function buildKey(string $name):string
    {
        return $this->getStoreKey() . $name;
    }

    protected function lock(string $name,int $tll = 0):bool
    {
        return $this->getStorage()->lock($this->buildKey($name),$tll);
    }

    protected function unlock(string $name):bool
    {
        return $this->getStorage()->unlock($this->buildKey($name));
    }

    protected function getNextId():?int
    {
        $localStorage = $this->getStorage();
        // 初始化预加载
        $this->initPreload();

        // 预加载还未完成,继续等待
        if ($this->preloaded === false) {
            return null;
        }

        $id = $localStorage->incrBy($this,$this->step);
        $maxId = (int)$localStorage->getValue($this->buildMaxidKey());
        // 当前号段已经消耗完,切换下一个号段
        if ($id > $maxId) {
            // 加载远程号段
            if($this->loadSwitchSegment()) {
                // 切换号段成功,重新获取id
                $id = $localStorage->incrBy($this,$this->step);
            } else {
                $id = null;
            }
        }

        return $id;
    }
}
